#include "Spheres.h"

#include <cassert>

#include <D3D10.h>

#include "Camera.h"
#include "CubeMapSphereEffect.h"
#include "D3DErrorChecker.h"
#include "EffectManager.h"
#include "GeometryGenerator.h"
#include "InputLayoutManager.h"
#include "SpheresEffect.h"
#include "TextureManager.h"

namespace D3D10Geometry
{
    Spheres::Spheres()
        : mDevice(nullptr)
        , mVertexBuffer(nullptr)
        , mIndexBuffer(nullptr)
        , mStoneMapRV(nullptr)
        , mBlackMapRV(nullptr)
        , mSpecularMapRV(nullptr)
        , mCubeMapRV(nullptr)
        , mNumVertices(0)
        , mNumIndices(0)
    {
        // Init translation and texture matrices
        DirectX::XMMATRIX translationMatrix = DirectX::XMMatrixTranslation(20.0f, 0.0f, 0.0f);
        DirectX::XMStoreFloat4x4(&mFirstSphereTranslationMatrix, translationMatrix);
        
        translationMatrix = DirectX::XMMatrixTranslation(-20.0f, 0.0f, 0.0f);
        DirectX::XMStoreFloat4x4(&mSecondSphereTranslationMatrix, translationMatrix);
        
        translationMatrix = DirectX::XMMatrixTranslation(0.0f, 0.0f, 20.0f);
        DirectX::XMStoreFloat4x4(&mThirdSphereTranslationMatrix, translationMatrix);
    
        translationMatrix = DirectX::XMMatrixTranslation(0.0f, 0.0f, -20.0f);
        DirectX::XMStoreFloat4x4(&mFourthSphereTranslationMatrix, translationMatrix);

        translationMatrix = DirectX::XMMatrixIdentity();
        DirectX::XMStoreFloat4x4(&mTextureMatrix, translationMatrix);
        DirectX::XMStoreFloat4x4(&mCubeMapSphereWorld, translationMatrix);       
    }

    Spheres::~Spheres()
    {
        mVertexBuffer->Release();
        mIndexBuffer->Release();
    }

    void Spheres::init(ID3D10Device * const device)
    {
        assert(device && "nullptr");
        mDevice = device;

        D3D10Utils::GeometryGenerator::MeshData sphere;
	    D3D10Utils::GeometryGenerator::createSphere(6.0f, 20, 20, sphere);
	    
	    // Cache the vertex offsets to each object in the concatenated vertex buffer.
        mNumVertices = static_cast<uint32_t> (sphere.mVertices.size());
        
	    // Cache the index count of each object.
        mNumIndices = static_cast<uint32_t> (sphere.mIndices.size());
      
        D3D10_BUFFER_DESC vertexBufferDesc;
        vertexBufferDesc.Usage = D3D10_USAGE_IMMUTABLE;
        vertexBufferDesc.ByteWidth = sizeof(D3D10Utils::GeometryGenerator::Vertex) * mNumVertices;
        vertexBufferDesc.BindFlags = D3D10_BIND_VERTEX_BUFFER;
        vertexBufferDesc.CPUAccessFlags = 0;
        vertexBufferDesc.MiscFlags = 0;

        D3D10_SUBRESOURCE_DATA initData;
        initData.pSysMem = &sphere.mVertices[0];
        HRESULT hr = mDevice->CreateBuffer(&vertexBufferDesc, &initData, &mVertexBuffer);
        D3D10Utils::ErrorChecker(hr);

        D3D10_BUFFER_DESC indexBufferDesc;
        indexBufferDesc.Usage = D3D10_USAGE_IMMUTABLE;
        indexBufferDesc.ByteWidth = sizeof(uint32_t) * mNumIndices;
        indexBufferDesc.BindFlags = D3D10_BIND_INDEX_BUFFER;
        indexBufferDesc.CPUAccessFlags = 0;
        indexBufferDesc.MiscFlags = 0;

        initData.pSysMem = &sphere.mIndices[0];
        hr = mDevice->CreateBuffer(&indexBufferDesc, &initData, &mIndexBuffer);
        D3D10Utils::ErrorChecker(hr);

        mStoneMapRV = D3D10Utils::GetTextureManager().getTextureRV(L"StaticCubeMapping/stone.dds");
        mSpecularMapRV = D3D10Utils::GetTextureManager().getTextureRV(L"StaticCubeMapping/defaultspec.dds");
        mBlackMapRV = D3D10Utils::GetTextureManager().getTextureRV(L"StaticCubeMapping/blackdiffuse.dds");
        mCubeMapRV = D3D10Utils::GetTextureManager().getCubeTextureRV(L"StaticCubeMapping/grassenvmap1024.dds");
    }

     void Spheres::draw()
     {
        const uint32_t stride = sizeof(D3D10Utils::GeometryGenerator::Vertex);
        const uint32_t offset = 0;
        mDevice->IASetVertexBuffers(0, 1, &mVertexBuffer, &stride, &offset);
        mDevice->IASetIndexBuffer(mIndexBuffer, DXGI_FORMAT_R32_UINT, 0);
        mDevice->IASetInputLayout(D3D10InputLayouts::InputLayoutManager::mSpheresInputLayout);
		mDevice->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
        
		// Update World * View * Projection matrix
        DirectX::XMMATRIX aux0 = DirectX::XMLoadFloat4x4(&D3D10Utils::GetCamera().getViewMatrix());
        DirectX::XMMATRIX aux1 = DirectX::XMLoadFloat4x4(&D3D10Utils::GetCamera().getProjectionMatrix());
        DirectX::XMMATRIX viewProjection = XMMatrixMultiply(aux0, aux1);
        aux1 = DirectX::XMLoadFloat4x4A(&mFirstSphereTranslationMatrix);
        aux0 = XMMatrixMultiply(aux1, viewProjection);
        DirectX::XMFLOAT4X4A worldViewProjection;
		DirectX::XMStoreFloat4x4A(&worldViewProjection, aux0);

        D3D10Effects::SpheresEffect * const spheresEffect = D3D10Effects::EffectManager::mSpheresEffect;
        D3D10_TECHNIQUE_DESC techDesc;
        spheresEffect->mTechnique->GetDesc(&techDesc);
		ID3D10EffectPass* pass = spheresEffect->mTechnique->GetPassByIndex(0);
        spheresEffect->setDiffuseMap(mStoneMapRV);
        spheresEffect->setSpecularMap(mSpecularMapRV);

        spheresEffect->setWorldViewProjectionMatrix(worldViewProjection);
        spheresEffect->setWorldMatrix(mFirstSphereTranslationMatrix);
        spheresEffect->setTextureMatrix(mTextureMatrix);
		pass->Apply(0);
        mDevice->DrawIndexed(mNumIndices, 0, 0);	

        // Update World * View * Projection matrix
        aux1 = DirectX::XMLoadFloat4x4A(&mSecondSphereTranslationMatrix);
        aux0 = XMMatrixMultiply(aux1, viewProjection);
		DirectX::XMStoreFloat4x4A(&worldViewProjection, aux0);               
        spheresEffect->setWorldViewProjectionMatrix(worldViewProjection);
        spheresEffect->setWorldMatrix(mSecondSphereTranslationMatrix);
		pass->Apply(0);
        mDevice->DrawIndexed(mNumIndices, 0, 0);

        // Update World * View * Projection matrix
        aux1 = DirectX::XMLoadFloat4x4A(&mThirdSphereTranslationMatrix);
        aux0 = XMMatrixMultiply(aux1, viewProjection);
		DirectX::XMStoreFloat4x4A(&worldViewProjection, aux0);               
        spheresEffect->setWorldViewProjectionMatrix(worldViewProjection);
        spheresEffect->setWorldMatrix(mThirdSphereTranslationMatrix);
		pass->Apply(0);
        mDevice->DrawIndexed(mNumIndices, 0, 0);

        // Update World * View * Projection matrix
        aux1 = DirectX::XMLoadFloat4x4A(&mFourthSphereTranslationMatrix);
        aux0 = XMMatrixMultiply(aux1, viewProjection);
		DirectX::XMStoreFloat4x4A(&worldViewProjection, aux0);               
        spheresEffect->setWorldViewProjectionMatrix(worldViewProjection);
        spheresEffect->setWorldMatrix(mFourthSphereTranslationMatrix);
		pass->Apply(0);
        mDevice->DrawIndexed(mNumIndices, 0, 0);

        //
        // Draw cube mapped sphere
        //
        mDevice->IASetInputLayout(D3D10InputLayouts::InputLayoutManager::mCubeMapSphereInputLayout);
        D3D10Effects::CubeMapSphereEffect * const cubeMapSphereEffect = D3D10Effects::EffectManager::mCubeMapSphereEffect;
        cubeMapSphereEffect->mTechnique->GetDesc(&techDesc);
		pass = cubeMapSphereEffect->mTechnique->GetPassByIndex(0);

        cubeMapSphereEffect->setCubeMap(mCubeMapRV);
        cubeMapSphereEffect->setDiffuseMap(mBlackMapRV);
        cubeMapSphereEffect->setSpecularMap(mSpecularMapRV);
        cubeMapSphereEffect->setTextureMatrix(mTextureMatrix);

        // Update World * View * Projection matrix
        aux1 = DirectX::XMLoadFloat4x4(&mCubeMapSphereWorld);
        aux0 = XMMatrixMultiply(aux1, viewProjection);
		DirectX::XMStoreFloat4x4(&worldViewProjection, aux0);               
        cubeMapSphereEffect->setWorldViewProjectionMatrix(worldViewProjection);
        cubeMapSphereEffect->setWorldMatrix(mCubeMapSphereWorld);
		pass->Apply(0);
        mDevice->DrawIndexed(mNumIndices, 0, 0);
     }
}
